/**
 * Copyright 2005 SIB Framework

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package org.sibframework.sda;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Time;
import java.util.Properties;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.sibframework.sda.manager.ModelManagerFactory;
import org.sibframework.sda.monitor.SdaMonitor;

/**
 * @author SongQuan
 *
 */
public class ConnectionFactory implements DataSource {
	

	public static final String DEFAULT_CONFIG_FILE = "jdbc.properties";
	
	//config
	//public String dbhost = "localhost";
	private String driver = null;
	private String url = null;
	private String dbuser = null;
	private String password = null;
	private boolean debug = false;

	//parameters for connection pool
	private int partitionCount = 1;
	private int minConnections = 1;
	private int maxConnections = 5;
	private int acquireIncrement = 1;
	private boolean lazy = true;
	private boolean useCache = true;
	
	//for init
	private boolean init = false;
	private static int retry    = 0;
	

	private static SdaMonitor monitor = null;
	
	
	
	public static SdaMonitor getMonitor() {
		return monitor;
	}
	
	
	public static void setMonitor(SdaMonitor amonitor) {
		ConnectionFactory.monitor = amonitor;
	}
	
	/**
	 * use default configuration file path.
	 */
	public ConnectionFactory() {
		//search configure file in install dir first.
		try {

			File configFile = new File(DEFAULT_CONFIG_FILE);
			if (configFile.exists() && configFile.isFile()) {
				this.init(new FileInputStream(configFile));
				return;
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace(logWriter);
		}
		
		//search configure file in class path then.
		InputStream is = this.getClass().getClassLoader().getResourceAsStream(ConnectionFactory.DEFAULT_CONFIG_FILE);
		this.init(is);
	}

	/**
	 * @param path configuration file path
	 */
	public ConnectionFactory(String path) {
		InputStream is = this.getClass().getClassLoader().getResourceAsStream(path);
		this.init(is);
	}
	
	/**
	 * @param is configuration file stream.
	 */
	public ConnectionFactory(InputStream is) {
		this.init(is);
	}
	
	/**
	 * create a database connection
	 * @return connection object
	 * @throws SQLException
	 */
	public synchronized Connection getConnection() throws SQLException {
		Connection conn = this.getConnection(dbuser, password);
		try {
			if (monitor != null) {
				monitor.addCreatedConnection();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * connect to a indicated server.
	 * @param dbuser
	 * @param password
	 * @return connection
	 * @throws SQLException
	 */
	public Connection getConnection(String dbuser, String password) throws SQLException {
		//Database driver name:
		String driverName = driver;
		if (debug) {
			logWriter.println("[" + new Time(System.currentTimeMillis()) + "] " + this.getClass().getName() + " :  Create connection : " + url);
			logWriter.flush();
		}
		try {
			Class.forName(driverName).newInstance();
			Properties prop = new Properties();
			
			//for Oracle 10g datatime
			prop.setProperty("oracle.jdbc.V8Compatible", "true");

			Connection conn = null;
			if (dbuser == null) {
				conn = DriverManager.getConnection(url, prop);
			} else {
				prop.setProperty("user", dbuser);
				prop.setProperty("password", password);
				conn = DriverManager.getConnection(url, prop);	
			}
			
			try {
				if (monitor != null) {
					monitor.addCreatedConnection();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			return conn;
			
		} 
		catch (SQLException ex) {
			ex.printStackTrace(logWriter);
			throw ex;
		} catch (Exception e) {
			e.printStackTrace(logWriter);
		}
		return null;
	}
	
	/**
	 * initial this factory use a give configuration file stream.
	 * @param is
	 */
	public void init(InputStream is) {

			try {
				//load from properties files
				Properties prop = new Properties();
				prop.load(is);
				driver = prop.getProperty("jdbc.driver");
				url = prop.getProperty("jdbc.url");
				dbuser = prop.getProperty("jdbc.username");
				password = prop.getProperty("jdbc.password");		

				partitionCount = Integer.parseInt(prop.getProperty("connpool.partitionCount"));
				minConnections = Integer.parseInt(prop.getProperty("connpool.minConnections"));
				maxConnections = Integer.parseInt(prop.getProperty("connpool.maxConnections"));
				acquireIncrement = Integer.parseInt(prop.getProperty("connpool.acquireIncrement"));	
				useCache = Boolean.parseBoolean(prop.getProperty("manager.useCache") == null? "true" : prop.getProperty("manager.useCache"));
				ModelManagerFactory.setUseCache(useCache);
				lazy = Boolean.parseBoolean(prop.getProperty("connpool.lazy") == null? "true" : prop.getProperty("connpool.lazy"));			
				
				this.debug = Boolean.valueOf(prop.getProperty("ds.debug"));
				init = true;
				is.close();
			} catch (Exception e) {
				e.printStackTrace();
				try {
					is.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				throw new RuntimeException(e);
			}

	}

	private PrintWriter logWriter = new PrintWriter(System.out);
	private int loginTimeout = 0;
	private Logger logger = null;
	@Override
	public PrintWriter getLogWriter() throws SQLException {
		return logWriter;
	}

	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
		this.logWriter = out;
	}

	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
		this.loginTimeout = seconds;
	}

	@Override
	public int getLoginTimeout() throws SQLException {
		return loginTimeout;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return logger;
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	public boolean isDebug() {
		return debug;
	}

	public void setDebug(boolean debug) {
		this.debug = debug;
	}

	public int getPartitionCount() {
		return partitionCount;
	}

	public void setPartitionCount(int partitionCount) {
		this.partitionCount = partitionCount;
	}

	public int getMinConnections() {
		return minConnections;
	}

	public void setMinConnections(int minConnections) {
		this.minConnections = minConnections;
	}

	public int getMaxConnections() {
		return maxConnections;
	}

	public void setMaxConnections(int maxConnections) {
		this.maxConnections = maxConnections;
	}

	public int getAcquireIncrement() {
		return acquireIncrement;
	}

	public void setAcquireIncrement(int acquireIncrement) {
		this.acquireIncrement = acquireIncrement;
	}

	public boolean isLazy() {
		return lazy;
	}

	public void setLazy(boolean lazy) {
		this.lazy = lazy;
	}
	

}
